/***********************************************************
 flabbybox - Music Player Client
 by Andrew Robinson <flabby@softhome.net>
 based on epmcd
 by Jeroen Massar <jeroen@unfix.org>
************************************************************
 $Author: flabby $
 $Id: $
 $Date: $
***********************************************************/

#include "flabbybox.h"
#include "menu.h"

#include <usb.h>
#include <errno.h>

#define _GNU_SOURCE

#define flabbybox_VERSION "0.12.1"
#define flabbybox_VSTRING "flabbybox %s by Andrew Robinson <flabby@softhome.net>\n"

#include "UsbLcd.h"

/* MPD functions */
#include "libmpdclient.h"
#define MPD_HOST_DEFAULT "musicplayer"
#define MPD_PORT_DEFAULT "2100"

usb_dev_handle *dh;

struct flabbybox_events	events[100];
unsigned int	maxevent = 0;
mpd_Connection	*mpd = NULL;
unsigned int	verbosity = 0, drop_uid = 0, drop_gid = 0;
bool			daemonize = true;
bool			running = true;
bool			exclusive = true;
char			*mpd_host = NULL, *mpd_port = NULL;

struct menu_entry *menu_pointer;

struct menu *flabbybox_menu;


char * namelist[10000];
char * locnlist[10000];
int mylistPointer=0;
int mylistLength=-1;

int scroll_pointer=0;

#define MaxStringLength 200
#define DisplayWidth 20
#define BrowseTime 20;

int DisplayMode = -1; /* 
			-1=playlist empty
			0=playing songs; 
			1=browsing albums - showing artists; 
			2=browsing albums - showing albums 
			3=browsing artists - showing artists
			4=browsing artists - showing songs  
			5=browsing playlists - showing playlists
			6=browsing current playlist - showing songs
			7=playing lastfm */

char line0[MaxStringLength+1];
char line1[MaxStringLength+1];
char line2[MaxStringLength+1];
char line3[MaxStringLength+1];

void updatedisplay();


/* When we receive a signal, we abort */
void handle_signal(int i)
{
	running = false;
	signal(i, &handle_signal);
}

void dologA(int level, const char *fmt, va_list ap)
{
	char buf[8192];

	if (level == LOG_DEBUG && verbosity < 1) return;

	vsnprintf(buf, sizeof(buf), fmt, ap);

	if (daemonize) syslog(LOG_LOCAL7|level, buf);
	else
	{
		FILE *out = (level == LOG_DEBUG || level == LOG_ERR ? stderr : stdout);
		fprintf(out, "[%6s] ",
			level == LOG_DEBUG ?    "debug" :
			(level == LOG_ERR ?     "error" :
			(level == LOG_WARNING ? "warn" :
			(level == LOG_NOTICE ?  "notice" :
			(level == LOG_INFO ?    "info" : "(!?)")))));
		fprintf(out, buf);
	}
}

void dolog(int level, const char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	dologA(level, fmt, ap);
	va_end(ap);
}

mpd_Connection *flabbybox_setup()
{
	int		iport;
	char		*test;
	int		port_env = 0;
	int		host_env = 0;
	int		password_len = 0;
	int		parsed_len = 0;
	mpd_Connection	*mpd = NULL;
	
	iport = strtol(mpd_port, &test, 10);
	if (iport <= 0 || test[0] != '\0')
	{
		dolog(LOG_ERR, "MPD_PORT \"%s\" is not a positive integer\n", mpd_port);
		return NULL;
	}

	/* parse password and host */
	test = strstr(mpd_host,"@");
        password_len = test-mpd_host;
	if (test) parsed_len++;

	if (!test) password_len = 0;
        if (test && password_len != 0) parsed_len += password_len;

	mpd = mpd_newConnection(mpd_host+parsed_len, iport, 10);
	if (!mpd) return NULL;

	if (mpd->error)
	{
		dolog(LOG_ERR, "MPD Connection Error: %s\n", mpd->errorStr);
		return NULL;
	}

	if (password_len)
	{
		char *pass = strdup(mpd_host);
		pass[password_len] = '\0';
		mpd_sendPasswordCommand(mpd, pass);
		mpd_finishCommand(mpd);
		free(pass);

		if (mpd->error)
		{
			dolog(LOG_ERR, "MPD Authentication Error: %s\n", mpd->errorStr);
			return NULL;
		}
	}

	return mpd;
}

bool mpd_check()
{
	if (!mpd->error) return false;

	/* Ignore timeouts */
	if (mpd->error != MPD_ERROR_CONNCLOSED)
	{
		dolog(LOG_WARNING, "MPD error: %s\n", mpd->errorStr);
	}

	/* Don't reconnect for non-fatal errors */
	if (mpd->error < 10 || mpd->error > 19)
	{
		return false;
	} 

	dolog(LOG_WARNING, "Attempting to close and reopen MPD connection\n");

	/* Close the old connection */
	mpd_closeConnection(mpd);

	/* Setup a new connection */
	mpd = flabbybox_setup();
	if (!mpd)
	{
		dolog(LOG_ERR, "MPD Connection Lost, exiting\n");
		exit(0);
		return false;

	}
	return true;
}

mpd_Status *flabbybox_status()
{
	int retry = 5;
	mpd_Status *s = NULL;


	while (retry > 0)
	{
		retry--;

		mpd_sendStatusCommand(mpd);
/*mpd_check() returns true if the connection was lost and succesfully reopened
therefore retry the command again*/
 		if (mpd_check()) continue;

		s = mpd_getStatus(mpd);
 		if (mpd_check()) continue;

		mpd_finishCommand(mpd);
		if (mpd_check()) continue;

		break;
	}

	return s;
}

/********************************************************************/

void f_exec(char *arg)
{
	system(arg);
}

#define F_CMDM(fn, f) \
void fn(char *arg)\
{\
	int retry = 5;\
\
	while (retry > 0)\
	{\
		retry--;\
		f;\
		if (mpd_check()) continue;\
		mpd_finishCommand(mpd);\
		if (mpd_check()) continue;\
		updatedisplay();\
		break;\
	}\
}

#define F_CMD(fn, f) F_CMDM(fn,f(mpd))

F_CMD(f_next, mpd_sendNextCommand)
F_CMD(f_prev, mpd_sendPrevCommand)
F_CMD(f_stop, mpd_sendStopCommand)
F_CMDM(f_play, 	mpd_executeCommand(mpd,"play\n"))

void f_seek(char *arg)
{
	int	dir = 0, seekto = 0, i = 0, retry = 5;
	bool	perc = false;

	mpd_Status *status = flabbybox_status(mpd);
	if (!status) return;

	if (arg[0] == '-')	{ i++; dir = -1; }
	else if (arg[0] == '+') { i++; dir = +1; }
	seekto = strlen(&arg[i]);
	if (arg[i+seekto] == '%') perc = true;

	seekto = atoi(&arg[i]);

	if (perc)
	{
		if (dir != 0) seekto = status->elapsedTime + ((status->totalTime * seekto / 100) * dir);
		else seekto = (status->totalTime * perc / 100);
	}
	else
	{
		if (dir != 0) seekto = status->elapsedTime + (seekto*dir);
		/* dir == 0 case is set correctly above */
	}

	/*
	 * Take care of limits
	 * (end-10 so that one can search till the end easily)
	 */
	if (seekto < 0 || seekto > (status->totalTime-10)) return;

	while (retry > 0)
	{
		retry--;
		mpd_sendSeekIdCommand(mpd, status->songid, seekto);
		if (mpd_check()) continue;
		mpd_finishCommand(mpd);
		if (mpd_check()) continue;
		break;
	}
	updatedisplay();
	mpd_freeStatus(status);
}

void f_pause(char *arg)
{
	int retry = 5, mode = 0;
	if (!arg)
	{
		/* Toggle the pause-play mode */
		mpd_Status *status = flabbybox_status(mpd);
		if (!status) return;

		mode = (status->state == MPD_STATUS_STATE_PAUSE ? 0 : 1);
		mpd_freeStatus(status);
	}
	else if (strcasecmp(arg, "on" ) == 0) mode = 1;
	else if (strcasecmp(arg, "off") == 0) mode = 0;

	while (retry > 0)
	{
		retry--;
		mpd_sendPauseCommand(mpd, mode);
		if (mpd_check()) continue;
		mpd_finishCommand(mpd);
		if (mpd_check()) continue;
		break;
	}
	updatedisplay();
}

void f_changevolume(char *arg)
{
	int retry = 5;
	int steps = atoi(&arg[0]);
	mpd_Status *status = flabbybox_status(mpd);
	if (!status) return;

	while (retry > 0)
	{
		retry--;
		mpd_sendSetvolCommand(mpd, status->volume+steps);
		if (mpd_check()) continue;
		mpd_finishCommand(mpd);
		if (mpd_check()) continue;
		break;
	}

	mpd_freeStatus(status);

}


void f_random(char *arg)
{
	int retry = 5, mode = 0;

	if (!arg)
	{
		/* Toggle the random mode */
		mpd_Status *status = flabbybox_status(mpd);
		if (!status) return;

		mode = !status->random;
		mpd_freeStatus(status);
	}
	else if (strcasecmp(arg, "on" ) == 0) mode = 1;
	else if (strcasecmp(arg, "off") == 0) mode = 0;

	while (retry > 0)
	{
		retry--;
		mpd_sendRandomCommand(mpd, mode);
		if (mpd_check()) continue;
		mpd_finishCommand(mpd);
		if (mpd_check()) continue;
		break;
	}
}

char * dirstrip(char * name)
{

	return(strrchr(name,'/')==NULL?name:strrchr(name,'/')+1);
}

void clearslists()
{
	mylistPointer = 0;
	while (!(mylistPointer>mylistLength)) {
		dolog(LOG_DEBUG, "Free %d\n",mylistPointer);
		free(namelist[mylistPointer]);
		free(locnlist[mylistPointer]);
		mylistPointer++;
	}
	mylistPointer=0;
	mylistLength=-1;
}

void loaddirlist(char *dir)
{
	mpd_InfoEntity * entity;

	clearslists();

	mpd_sendLsInfoCommand(mpd, dir);
	mpd_check();

	namelist[0] = strdup("[ALBUMS]");
	locnlist[0] = strdup(dir);
	mylistPointer = 1;

	dolog(LOG_DEBUG,"Loading %s\n",dir);

	while((entity = mpd_getNextInfoEntity(mpd))) {
		if(entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY) {
			locnlist[mylistPointer] = strdup(entity->info.directory->path);
			namelist[mylistPointer] = strdup(dirstrip(locnlist[mylistPointer]));
			dolog(LOG_DEBUG,"%d %s %s\n", mylistPointer, locnlist[mylistPointer], namelist[mylistPointer]);
			mylistPointer++;				
		}
		mpd_freeInfoEntity(entity);
	}

	mpd_finishCommand(mpd);
	mylistLength = mylistPointer-1;
 
	dolog(LOG_DEBUG,"%s: Total count %d\n", namelist[0], mylistLength);

}

void loadsonglist(char *artist)
{
	mpd_InfoEntity * entity;

	mpd_sendSearchCommand(mpd, MPD_TABLE_ARTIST, artist);
	mpd_check();

	clearslists();

	namelist[0] = strdup("Choose song ...");
	locnlist[0] = strdup("[NONE]");
	mylistPointer = 1;

	dolog(LOG_DEBUG,"Loading songs by %s\n",artist);

	while((entity = mpd_getNextInfoEntity(mpd))) {
		if(entity->type==MPD_INFO_ENTITY_TYPE_SONG) {
			namelist[mylistPointer] = strdup(entity->info.song->title);
			locnlist[mylistPointer] = strdup(entity->info.song->file);
			dolog(LOG_DEBUG,"%d %s %s\n", mylistPointer, locnlist[mylistPointer], namelist[mylistPointer]);
			mylistPointer++;				
		}
		mpd_freeInfoEntity(entity);
	}

	mpd_finishCommand(mpd);
	mylistLength = mylistPointer-1;
 
	dolog(LOG_DEBUG,"%s: Total count %d\n", namelist[0], mylistLength);

}


static int cmpr(const void *a, const void *b) { 
 return strcmp(*(char **)a, *(char **)b);
}

void sortstrarr(void *array, unsigned n) { 
 qsort(array, n, sizeof(char *), cmpr);
}


void loadartistlist()
{
	char *entity;
	int i;

	mpd_sendListCommand(mpd, MPD_TABLE_ARTIST, NULL);
	mpd_check();

	clearslists();

	namelist[0] = strdup(" ");
	locnlist[0] = strdup(" ");
	mylistPointer = 1;

	dolog(LOG_DEBUG,"Loading ARTISTS\n");

	while((entity = mpd_getNextArtist(mpd))) {
		namelist[mylistPointer] = entity;
		locnlist[mylistPointer] = strdup("[NONE]");
		dolog(LOG_DEBUG,"%d %s\n", mylistPointer, namelist[mylistPointer]);
		mylistPointer++;				
	}

	mpd_finishCommand(mpd);
	mylistLength = mylistPointer-1;

	sortstrarr(namelist, mylistLength);

	free(namelist[0]);
	namelist[0] = strdup("[ARTISTS]");

/*	locnlist[0] = strdup("[NONE]");*/

/*	  for (i=0 ; i < mylistLength ; i++)
   {
		dolog(LOG_DEBUG,"%d %s\n", i, namelist[i]);
}
 
	dolog(LOG_DEBUG,"%s: Total count %d\n", namelist[0], mylistLength);
*/
}

void loadplaylistlist(char *dir)
{
	mpd_InfoEntity * entity;

	clearslists();

	mpd_sendLsInfoCommand(mpd, dir);
	mpd_check();

	namelist[0] = strdup("[PLAYLISTS]");
	locnlist[0] = strdup(dir);
	mylistPointer = 1;

	dolog(LOG_DEBUG,"Loading %s\n",dir);

	while((entity = mpd_getNextInfoEntity(mpd))) {
		if(entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE) {
			locnlist[mylistPointer] = strdup(entity->info.playlistFile->path);
			namelist[mylistPointer] = strdup(dirstrip(locnlist[mylistPointer]));
			dolog(LOG_DEBUG,"%d %s %s\n", mylistPointer, locnlist[mylistPointer], namelist[mylistPointer]);
			mylistPointer++;				
		}
		mpd_freeInfoEntity(entity);
	}

	mpd_finishCommand(mpd);
	mylistLength = mylistPointer-1;
 
	dolog(LOG_DEBUG,"%s: Total count %d\n", namelist[0], mylistLength);

}

void loadcurrentlist()
{
	mpd_InfoEntity * entity;

	clearslists();

	mpd_sendPlaylistInfoCommand(mpd, -1);
	mpd_check();

	namelist[0] = strdup("[CURRENT PLAYLIST]");
	locnlist[0] = strdup(" ");
	mylistPointer = 1;

	dolog(LOG_DEBUG,"Loading current playlist\n");

	while((entity = mpd_getNextInfoEntity(mpd))) {
		if(entity->type==MPD_INFO_ENTITY_TYPE_SONG) {
			namelist[mylistPointer] = (char *) malloc(MaxStringLength+1);
			sprintf(namelist[mylistPointer], "%s:%s - %s", entity->info.song->track, entity->info.song->title, entity->info.song->artist);
			locnlist[mylistPointer] = (char *) malloc(MaxStringLength+1);
			sprintf(locnlist[mylistPointer], "%d", entity->info.song->id);
			dolog(LOG_DEBUG,"%d %s %s\n", mylistPointer, locnlist[mylistPointer], namelist[mylistPointer]);
			mylistPointer++;				
		}
		mpd_freeInfoEntity(entity);
	}

	mpd_finishCommand(mpd);
	mylistLength = mylistPointer-1;
 
	dolog(LOG_DEBUG,"%s: Total count %d\n", namelist[0], mylistLength);

}



void f_browse(char *arg)
{
	switch (DisplayMode) {
	case -1:
		loaddirlist("/");
		DisplayMode = 1;
		break;
	case 0:
		loadcurrentlist();
		DisplayMode = 6;
		break;
	case 6:
		loaddirlist("/");
		DisplayMode = 1;
		break;
	case 1: 
		loadartistlist();
		DisplayMode = 3;
		break;
	case 3: 
		loadplaylistlist("/");
		DisplayMode = 5;
		break;
	default:
		clearslists();
		DisplayMode = 0;
		break;
	}
	mylistPointer = 0;
	updatedisplay();

}

void f_browsestep(char *arg)
{

	int steps = atoi(&arg[0]);

	mylistPointer -= steps;

	if(mylistPointer<0) {
		mylistPointer+=mylistLength+1;
	}
	if(mylistPointer>mylistLength) {
		mylistPointer-=mylistLength;
	}

	scroll_pointer = 0;

	updatedisplay();
}

void f_browsesel(char *arg)
{
char * sname;
char * slocn;

	sname = strdup(namelist[mylistPointer]);
	slocn = strdup(locnlist[mylistPointer]);

	dolog(LOG_DEBUG,"You have selected #%d %s %s\n", mylistPointer, sname, slocn);
	switch(DisplayMode) {
		case 1:
			loaddirlist(slocn);
			mylistPointer = 0;
			DisplayMode = 2;
			break;
		case 2: case 4:
			if (mylistPointer>0){
				mpd_sendClearCommand(mpd); 
				mpd_finishCommand(mpd);
				mpd_sendAddCommand(mpd, slocn);
				mpd_finishCommand(mpd);
				mpd_sendPlayCommand(mpd,0);
				mpd_finishCommand(mpd);
				mpd_check();
			}
			DisplayMode = 0;
			break;
		case 5:
			if (mylistPointer>0){
				mpd_sendClearCommand(mpd); 
				mpd_finishCommand(mpd);
				mpd_sendLoadCommand(mpd, slocn);
				mpd_finishCommand(mpd);
				mpd_sendPlayCommand(mpd,0);
				mpd_finishCommand(mpd);
				mpd_check();
			}
			DisplayMode = 0;
			break;
		case 6:
			if (mylistPointer>0){
				mpd_sendPlayIdCommand(mpd,atoi(slocn));
				mpd_finishCommand(mpd);
				mpd_check();
			}
			DisplayMode = 0;
			break;
		case 3:
			loadsonglist(sname);
			mylistPointer = 0;
			DisplayMode = 4;
			break;

		default:
			break;
	}
	
	updatedisplay();

	free(sname);
	free(slocn);
}

struct flabbybox_funcs
{
	char *name;
	void (*function)(char *arg);
	char *format;
	char *label;
} func_map[] =
{
	{"EXEC",	f_exec,		"exec <shellcmd>",		"Execute a command"},
	{"MPD_NEXT",	f_next,		"mpd_next",			"MPD Next Track"},
	{"MPD_PREV",	f_prev,		"mpd_prev",			"MPD Previous Track"},
	{"MPD_STOP",	f_stop,		"mpd_stop",			"MPD Stop Playing"},
	{"MPD_PLAY",	f_play,		"mpd_play",			"MPD Start Playing"},
	{"VOLUME_STEP",	f_changevolume,	"volume_step",			"MPD Change Volume (+|-) steps"},
	{"MPD_PAUSE",	f_pause,	"mpd_pause [on|off]",		"MPD Pause Toggle or Set"},
	{"MPD_SEEK",	f_seek,		"mpd_seek [+|-]<val>[%]",	"MPD Seek direct or relative (+|-) percentage when ends in %"},
	{"MPD_RANDOM",	f_random,	"mpd_random [on|off]",		"MPD Random Toggle or Set"},
	{"BROWSE",	f_browse,	"browse",			"BROWSE Start Browsing"},
	{"BROWSE_STEP",	f_browsestep,	"browse_step",			"BROWSE Move Selection (+|-) steps"},
	{"BROWSE_SEL",	f_browsesel,	"browse_sel",			"BROWSE Select"},
	{NULL,		NULL,		NULL,					"undefined"}
};

/********************************************************************/

bool set_event(uint16_t type, uint16_t code, int32_t value, void (*action)(char *arg), char *args)
{
	if (maxevent >= (sizeof(events)/sizeof(events[0])))
	{
		dolog(LOG_ERR, "Maximum number of events reached\n");
		return false;
	}

	events[maxevent].type = type;
	events[maxevent].code = code;
	events[maxevent].value = value;
	events[maxevent].action = action;
	events[maxevent].args = args ? strdup(args) : args;

	maxevent++;
	return true;
}

/*
	KEY_KPSLASH RELEASE f_seek -1
	<key> <value> <action> <arg>
*/

bool set_event_from_map(char *buf, struct flabbybox_mapping *event_map, struct flabbybox_mapping *value_map)
{
	unsigned int i, o = 0, len = strlen(buf), l, event = 0, value = 0, func = 0;
	void (*what)(char *arg);
	char *arg = NULL;

	for (i=0; event_map[i].code != flabbybox_MAPPING_END; i++)
	{
		l = strlen(event_map[i].name);
		if (len < o+l || buf[o+l] != ' ') continue;
		if (strncasecmp(&buf[o], event_map[i].name, l) == 0) break;
	}

	if (event_map[i].code == flabbybox_MAPPING_END)
	{
		dolog(LOG_DEBUG, "Undefined Code at %u in '%s'\n", o, buf);
		return false;
	}
	event = i;

	o += l+1;
	for (i=0; value_map[i].code != flabbybox_MAPPING_END; i++)
	{
		l = strlen(value_map[i].name);
		if (len < o+l || buf[o+l] != ' ') continue;
		if (strncasecmp(&buf[o], value_map[i].name, l) == 0) break;
	}

	if (value_map[i].code == flabbybox_MAPPING_END)
	{
		dolog(LOG_DEBUG, "Undefined Key Value at %u in '%s'\n", o, buf);
		return false;
	}
	value = i;

	o += l+1;
	for (i=0; func_map[i].name != NULL; i++)
	{
		l = strlen(func_map[i].name);
		if (len != o+l && (len < o+l || buf[o+l] != ' ')) continue;
		if (strncasecmp(&buf[o], func_map[i].name, l) == 0) break;
	}

	if (func_map[i].name == NULL)
	{
		dolog(LOG_DEBUG, "Undefined Function at %u in '%s'\n", o, buf);
		return false;
	}
	func = i;

	o += l+1;
	if (len > o) arg = &buf[o];

	dolog(LOG_DEBUG, "Mapping Event %s (%s) %s (%s) to do %s (%s) with arg %s\n",
		event_map[event].name, event_map[event].label,
		value_map[value].name, value_map[value].label,
		func_map[func].name, func_map[func].label,
		arg ? arg : "<none>");

	return set_event(EV_KEY, event_map[event].code, value_map[value].code, func_map[func].function, arg);
}

/********************************************************************/

/*
	 0 = failed to open file
	 1 = all okay
	-1 = error parsing file
*/
int readconfig(char *cfgfile, char **device)
{
	unsigned int line = 0;

	FILE *f = fopen(cfgfile, "r");

	dolog(LOG_DEBUG, "ReadConfig(%s) = %s\n", cfgfile, f ? "ok" : "error");

	if (!f) return 0;

	while (!feof(f))
	{
		char buf[1024], buf2[1024];
		unsigned int n, i = 0, j = 0;

		line++;

		if (fgets(buf2, sizeof(buf2), f) == 0) break;
		n = strlen(buf2)-1;

		/*
		 * Trim whitespace
		 * - Translate \t to space
		 * - strip multiple whitespaces
		 * Saves handling them below
		 */
		for (i=0,j=0;i<n;i++)
		{
			if (buf2[i] == '\t') buf2[i] = ' ';
			if ((i == 0 || (i > 0 && buf2[i-1] == ' ')) &&
				(buf2[i] == ' ' || buf2[i] == '\t'))
			{
				continue;
			}
			buf[j++] = buf2[i];
		}
		/* Trim trailing space if it is there */
		if (j>0 && buf[j-1] == ' ') j--;
		/* Terminate our new constructed string */
		buf[j] = '\0';
		n = j;

		/* Empty or comment line? */
		if (	n == 0 ||
			buf[0] == '#' ||
			(buf[0] == '/' && buf[1] == '/'))
		{
			continue;
		}

		dolog(LOG_DEBUG, "%s@%04u: %s\n", cfgfile, line, buf);

		if (strncasecmp("mpd_host ", buf, 9) == 0)
		{
			dolog(LOG_DEBUG, "Setting MPD_HOST to %s\n", &buf[9]);
			if (mpd_host) free(mpd_host);
			mpd_host = strdup(&buf[9]);
		}
		else if (strncasecmp("mpd_port ", buf, 9) == 0)
		{
			dolog(LOG_DEBUG, "Setting MPD_PORT to %s\n", &buf[9]);
			if (mpd_port) free(mpd_port);
			mpd_port = strdup(&buf[9]);
		}
		else if (strncasecmp("eventdevice ", buf, 12) == 0)
		{
			if (*device) free(*device);
			*device = strdup(&buf[12]);
		}
		else if (strncasecmp("exclusive ", buf, 10) == 0)
		{
			if (strncasecmp("on", &buf[10], 2) == 0) exclusive = true;
			else if (strncasecmp("off", &buf[10], 3) == 0) exclusive = false;
			else
			{
				dolog(LOG_ERR, "Exclusive is either 'on' or 'off'\n");
				return -1;
			}
		}
		else if (strncasecmp("exclusive", buf, 9) == 0)
		{
			exclusive = true;
		}
		else if (strncasecmp("nonexclusive", buf, 12) == 0)
		{
			exclusive = false;
		}
		else if (strncasecmp("key ", buf, 4) == 0)
		{
			if (!set_event_from_map(&buf[4], key_event_map, key_value_map)) return -1;
		}
		else if (strncasecmp("menu ", buf, 5) == 0)
		{
			flabbybox_menu = add_menu(flabbybox_menu, &buf[4]);
		}
/*		else if (strncasecmp("menu-entry ", buf, 5) == 0)
		{
			add_menu(&buf[4]);
		} */
		else if (strncasecmp("user ", buf, 5) == 0)
		{
			struct passwd *passwd;

			/* setuid()+setgid() to another user+group */
			passwd = getpwnam(&buf[5]);
			if (passwd)
			{
				drop_uid = passwd->pw_uid;
				drop_gid = passwd->pw_gid;
			}
			else
			{
				dolog(LOG_ERR, "Couldn't find user %s\n", optarg);
				return -1;
			}
		}
		else
		{
			dolog(LOG_ERR, "Unrecognized configuration line %u: %s\n", line, buf);
			return -1;
		}
	}

	fclose(f);
		mylistPointer--;

	return 1; 
}

/* Long options */
static struct option const long_options[] = {
	{"config",		required_argument,	NULL, 'c'},
	{"daemonize",		no_argument,		NULL, 'd'},
	{"nodaemonize",		no_argument,		NULL, 'f'},
	{"eventdevice",		required_argument,	NULL, 'e'},
	{"help",		no_argument,		NULL, 'h'},
	{"list-keys",		no_argument,		NULL, 'K'},
	{"list-functions",	no_argument,		NULL, 'L'},
	{"quiet",		no_argument,		NULL, 'q'},
	{"user",		required_argument,	NULL, 'u'},
	{"verbose",		no_argument,		NULL, 'v'},
	{"version",		no_argument,		NULL, 'V'},
	{"verbosity",		required_argument,	NULL, 'y'},
	{"exclusive",		no_argument,		NULL, 'x'},
	{"nonexclusive",	no_argument,		NULL, 'X'},
	{NULL,			no_argument,		NULL, 0},
};

static char short_options[] = "c:de:fhKLqu:vVy:";

static struct
{
	char *args;
	char *desc;
} desc_options[] =
{
	{"<file>",		"Configuration File Location"},
	{NULL,			"Detach the program into the background"},
	{NULL,			"Don't detach, stay in the foreground"},
	{"<eventdevice>",	"The event device to use, default: /dev/input/event0"},
	{NULL,			"This help"},
	{NULL,			"List the keys that are known to this program"},
	{NULL,			"List the functions known to this program"},
	{NULL,			"Lower the verbosity level to 0 (quiet)"},
	{"<username>",		"Drop priveleges to <user>"},
	{NULL,			"Increase the verbosity level by 1"},
	{NULL,			"Show the version of this program"},
	{"<level>",		"Set the verbosity level to <level>"},
	{NULL,			"Exclusive device access (default)"},
	{NULL,			"Non-Exclusive device access"},
	{NULL,			NULL}
};

void displayline(int row, char *line, bool scroll, bool cursor)

{
	int n, m;
	char displayline[DisplayWidth+1];

/*	dolog(LOG_DEBUG,"displayline : %d : %s\n", row, line );*/


	n = strlen(line);
	/* if it's greater than DisplayWidth long and 'scroll' is true ... */
	if ((n>DisplayWidth) & scroll) {

	 m = n - DisplayWidth;
	 m = scroll_pointer % (m+1);

	/* ... rotate by scrollpointer positions and trim to DisplayWidth */
		snprintf(displayline,DisplayWidth+1, "%s", line + m);

	}
	else /* else pad with spaces */
	{
		snprintf(displayline,DisplayWidth+1, "%-20s", line);
	}

	if (cursor) {
		displayline[0] = '>';
	}

	/* write out the line */

	UsbLcd_write_at(row,0,displayline);

	if (row==0) scroll_pointer++;

}

void updateplaydisplay()
{

	mpd_Status * status;
	mpd_InfoEntity * entity;

	DisplayMode = -1;

      snprintf(line0,MaxStringLength, "                                                                         ");
      snprintf(line1,MaxStringLength, "                                                                         ");
      snprintf(line2,MaxStringLength, "                                                                         ");
      snprintf(line3,MaxStringLength, "                                                                         ");


      if (!mpd) {

           snprintf(line3,MaxStringLength, "Not Connected ... ");
	}

      else if (mpd->error) {

           snprintf(line3,MaxStringLength, "%s", mpd->errorStr);
	}

      else if ((status = flabbybox_status())) {

   	   if(status->state == MPD_STATUS_STATE_PLAY || status->state == MPD_STATUS_STATE_PAUSE) {

		DisplayMode = 0;

        	if(status->state == MPD_STATUS_STATE_PLAY) {
                  snprintf(line3, MaxStringLength, "Playing %d:%d",status->elapsedTime, status->totalTime);
		}
		else {  
			snprintf(line3, MaxStringLength, "Paused  %d:%d",status->elapsedTime, status->totalTime);
		}

		mpd_sendCurrentSongCommand(mpd);

		while((entity = mpd_getNextInfoEntity(mpd))) {

			if(entity->type!=MPD_INFO_ENTITY_TYPE_SONG) { 
				mpd_freeInfoEntity(entity);
            		dolog(LOG_DEBUG,"Not OK!\n");
				continue;
			}

			if(entity->info.song->artist) {
                               snprintf(line0,MaxStringLength, "%s",entity->info.song->artist); 
			}
			if(entity->info.song->album) {
                               snprintf(line1,MaxStringLength, "%s",entity->info.song->album); 
			} 
			if(entity->info.song->title) {
                               snprintf(line2,MaxStringLength, "%s: %s",entity->info.song->track, entity->info.song->title);
			}

			mpd_freeInfoEntity(entity);
		}
		mpd_finishCommand(mpd);
	   } 
	   else
	   { /*                                    12345678901234567890 */
                snprintf(line0,MaxStringLength, " #   ##          #  ");
                snprintf(line1,MaxStringLength, "    #  # #  #  ###  ");
                snprintf(line2,MaxStringLength, " #  ###  #  # #  #  ");
                snprintf(line3,MaxStringLength, " #  #     ###  ##   ");

	   }
		mpd_freeStatus(status);
        }
        else
	{
           snprintf(line3,MaxStringLength, "Status Error  ...                                         ");
	}

	displayline(0,line0, true, false);
	displayline(1,line1, false, false);
	displayline(2,line2, true, false);
	displayline(3,line3, false, false); 

/*	dolog(LOG_DEBUG,"%s\n%s\n%s\n%s\n", line0, line1, line2, line3);*/


}


void updatebrowsedisplay()

{
int p;

	p = mylistPointer / 4; 
	p = p * 4;

      snprintf(line0,MaxStringLength, "                                                                         ");
      snprintf(line1,MaxStringLength, "                                                                         ");
      snprintf(line2,MaxStringLength, "                                                                         ");
      snprintf(line3,MaxStringLength, "                                                                         ");
 
	if (p<mylistLength+1) {
	        snprintf(line0, MaxStringLength, " %s", namelist[p]);
	}

	displayline(0,line0, (p==mylistPointer), (p==mylistPointer));

	if (++p<mylistLength+1) {
	        snprintf(line1, MaxStringLength, " %s", namelist[p]);
	}

	displayline(1,line1, (p==mylistPointer), (p==mylistPointer));

	if (++p<mylistLength+1) {
	        snprintf(line2, MaxStringLength, " %s", namelist[p]);
	}
	displayline(2,line2, (p==mylistPointer), (p==mylistPointer));


	if (++p<mylistLength+1) {
	        snprintf(line3, MaxStringLength, " %s", namelist[p]);
	}

	displayline(3,line3, (p==mylistPointer), (p==mylistPointer)); 

}

void updatedisplay()

{
/*	dolog(LOG_DEBUG,"Display mode = %d\n", DisplayMode);*/

	switch (DisplayMode) {
		case 0: case -1: 
			updateplaydisplay();
			break;
		case 1: case 2: case 3: case 4: case 5: case 6:
			updatebrowsedisplay();
			break;
		default:
			updateplaydisplay();
			break;

		}

}

/* Find USB IR device and open device handle.
 */
static usb_dev_handle *find_dev()
{

/* Default USB device
 */
#define USB_VENDOR  0x147a
#define USB_PRODUCT 0xe019

  struct usb_bus *busses, *bus;
  usb_dev_handle *dh;
  int e;

  usb_init();
  usb_find_busses();
  usb_find_devices();
    
  busses = usb_get_busses();
  if (busses == 0) {
    dolog(LOG_ERR, "usblib: nothing there!\n");
    return 0;
  }

  for (bus = busses; bus; bus = bus->next) {
    struct usb_device *dev;
    
    for (dev = bus->devices; dev; dev = dev->next) {
      if (dev->descriptor.idVendor == USB_VENDOR &&
	  dev->descriptor.idProduct == USB_PRODUCT) {

	int c, i, a;
	char driver[20];

        dolog(LOG_DEBUG, "USB device found.\n");

	if (geteuid() != 0)
	  dolog(LOG_WARNING, "Warning: normally, only root can program USB devices.\n");

	/* Only one configuration, interface and alt. setting */
	c = 1;
	i = a = 0;

	/* Open the device,
	   set the alternate setting,
	   and claim the interface */
	dh = usb_open(dev);

	e = usb_get_driver_np(dh, 0, driver, 20);
	if (e == -ENODATA) {
	    dolog(LOG_DEBUG, "OK, device interface not in use.\n");
	} else if (e < 0) {
	  dolog(LOG_ERR, "Can't find current driver: %s\n", strerror(-e));
	  return 0;
	} else {
	  dolog(LOG_DEBUG,
		    "Interface claimed by '%s', trying to detach\n", driver);
	  e = usb_detach_kernel_driver_np(dh, i);
	  if (e < 0) {
	    dolog(LOG_ERR, "Can't detach interface: %s\n", strerror(-e));
	    return 0;
	  }
	}

	e = usb_set_configuration(dh, 1);
	if (e < 0) {
	  dolog(LOG_ERR, "Can't set configuration: %s\n", strerror(-e));
	  return 0;
	}

	e = usb_claim_interface(dh, i);
	if (e < 0) {
	  dolog(LOG_ERR, "Can't claim interface: %s\n", strerror(-e));
	  return 0;
	}
	dolog(LOG_DEBUG, "Claimed USB device interface.\n");

	e = usb_set_altinterface(dh, a);
	if (e < 0) {
	  dolog(LOG_ERR, "Can't set altinterface: %s\n", strerror(-e));
	  return 0;
	}

	return dh;
      }
    }
  }

  dolog(LOG_ERR, "Sorry, can't find device.\n");
  return 0;
}



int read_device(usb_dev_handle *dev)
{
  int timeout = 200;
  unsigned char x[4];

/* The device sends 4 byte packets.
 *  {0x00 0xf7 KeyNum ~KeyNum}
 *
 * Zol100: key numbers range from 0x00 to 0x0d (14 keys)
 */
  
    int e = usb_interrupt_read(dev, 1, (char *)x, 4, timeout);

/*    dolog(LOG_DEBUG, "usb_interrupt_read returned %d\n", e); */


    if (e < 0) {
      if (e == -ETIMEDOUT) {
/*	dolog(LOG_DEBUG, "Repeat timeout\n"); */
      }
      else {
	dolog(LOG_ERR, "Interrupt read: %s\n", strerror(-e));
      } 
    }
    else if (e == 4) {
      dolog(LOG_DEBUG, "USB message: {%02x %02x %02x %02x}.\n", x[0], x[1], x[2], x[3]);
      e = x[2];
      }
    else {
      dolog(LOG_DEBUG, "Short read: %d bytes.\n", e);
      e = 0;
    }

    return e;
  
}


int read_key()

{
int e;
int keys[] = {
KEY_POWER, KEY_UP, KEY_BACK, KEY_LEFT, KEY_ENTER, KEY_RIGHT, KEY_PREVIOUSSONG,
KEY_DOWN, KEY_NEXTSONG, KEY_STOP, KEY_PLAYPAUSE, KEY_VOLUMEUP, KEY_MENU, KEY_VOLUMEDOWN
}; 

	e=read_device(dh);
/*    dolog(LOG_DEBUG, "read_device returned %d\n", e);*/


	if (e < 0) {
	return e;
	}
	else {
	return keys[e];
	}

}


int main (int argc, char **argv)
{
	int			fd = -1, option_index, j , timeoutcount;
	char			*device = NULL, *cfgfile = NULL, *conffile = NULL, *t;
	struct input_event	ev, prev;
	struct flabbybox_events	*evt;
	unsigned int		i, repeat = 0;

	while ((j = getopt_long(argc, argv, short_options, long_options, &option_index)) != EOF)
	{
		switch (j)
		{
		case 'c':
			if (conffile) free(conffile);
			conffile = strdup(optarg);
			break;
		case 'd':
			daemonize = true;
			break;
		case 'f':
			daemonize = false;
			break;
		case 'e':
			if (device) free(device);
			device = strdup(optarg);
			break;
		case 'h':
			fprintf(stderr, "usage: %s\n", argv[0]);
			for (i=0; long_options[i].name; i++)
			{
				char buf[3] = "  ";
				if (long_options[i].val != 0)
				{
					buf[0] = '-';
					buf[1] = long_options[i].val;
				}
				fprintf(stderr, "%2s -%-15s %-15s %s\n",
					buf,
					long_options[i].name,
					desc_options[i].args ? desc_options[i].args : "",
					desc_options[i].desc ? desc_options[i].desc : "");
			}
			return 1;
		case 'L':
			for (i=0; func_map[i].name; i++)
			{
				fprintf(stderr, "%-25s %s\n", func_map[i].format, func_map[i].label);
			}
			return 0;
		case 'K':
			for (i=0; key_event_map[i].code != flabbybox_MAPPING_END; i++)
			{
				fprintf(stderr, "%-25s %s\n", key_event_map[i].name, key_event_map[i].label);
			}
			return 0;
		case 'q':	
			verbosity++;
			break;
		case 'u':
			{
				struct passwd *passwd;
				/* setuid()+setgid() to another user+group */
				passwd = getpwnam(optarg);
				if (passwd)
				{
					drop_uid = passwd->pw_uid;
					drop_gid = passwd->pw_gid;
				}
				else
				{
					dolog(LOG_ERR, "Couldn't find user %s\n", optarg);
					return 1;
				}
			}
			break;
		case 'v':
			verbosity++;
			break;
		case 'y':
			verbosity = atoi(optarg);
			break;
		case 'V':
			fprintf(stderr, flabbybox_VSTRING, flabbybox_VERSION);
			return 1;
		case 'x':
			exclusive = true;
			break;
		case 'X':
			exclusive = false;
			break;
		default:
			if (j != 0) fprintf(stderr, "Unknown short option '%c'\n", j);
			else fprintf(stderr, "Unknown long option\n");
			fprintf(stderr, "See '%s -h' for help\n", argv[0]);
			return 1;
		}
	}

	dolog(LOG_INFO, flabbybox_VSTRING, flabbybox_VERSION);

	if (!device) device = strdup("/dev/input/event0");

	if ((t = getenv("MPD_HOST"))) mpd_host = strdup(t);
	else mpd_host = strdup(MPD_HOST_DEFAULT);
	if ((t = getenv("MPD_PORT"))) mpd_port = strdup(t);
	else mpd_port = strdup(MPD_PORT_DEFAULT);

	if (!conffile)
	{
		/* Try user's config */
		cfgfile = getenv("HOME");
		if (cfgfile)
		{
			char buf[256];
			snprintf(buf, sizeof(buf), "%s/%s", cfgfile, ".flabbybox.conf");
			cfgfile = conffile = strdup(buf);
			j = readconfig(cfgfile, &device);
		}
		else j = 0;
		if (j == 0)
		{
			cfgfile = "/etc/flabbybox.conf";
			j = readconfig(cfgfile, &device);
		}
	}
	else
	{
		/* Try specified config */
		cfgfile = conffile;
		j = readconfig(cfgfile, &device);
	}

	if (j != 1)
	{
		if (j == 0) dolog(LOG_ERR, "Configuration file '%s' not found\n", cfgfile);
		else if (j == -1) dolog(LOG_ERR, "Parse error in configuration file '%s'\n", cfgfile);
		if (conffile) free(conffile);
		if (device) free(device);
		return 1;
	}

	if (conffile) free(conffile);

	if (daemonize)
	{
		j = fork();
		if (j < 0)
		{
			dolog(LOG_ERR, "Couldn't fork for daemonization\n");
			return 1;
                }

		/* Exit the mother fork */
		if (j != 0) return 0;

		/* Child fork */
		setsid();

		/* Cleanup stdin/out/err */
		freopen("/dev/null","r",stdin);
		freopen("/dev/null","w",stdout);
		freopen("/dev/null","w",stderr);
	}


	/* Handle these signals for a clean exit */
	signal(SIGHUP,  &handle_signal);
	signal(SIGTERM, &handle_signal);
	signal(SIGINT,  &handle_signal);
	signal(SIGKILL, &handle_signal);

	/* Ignore some odd signals */
	signal(SIGILL,  SIG_IGN);
	signal(SIGABRT, SIG_IGN);
	signal(SIGUSR1, SIG_IGN);
	signal(SIGUSR2, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
	signal(SIGSTOP, SIG_IGN);
	signal(SIGTSTP, SIG_IGN);
	signal(SIGTTIN, SIG_IGN);
	signal(SIGTTOU, SIG_IGN);
        signal(SIGUSR1, SIG_IGN);
        signal(SIGUSR2, SIG_IGN);

	/* Try to open the device 
	fd = open(device, O_RDONLY);
	if (fd < 0)
	{
		perror("Couldn't open event device");
		return 1;
	}
	free(device);
	device = NULL; 


	 Obtain Exclusive device access 
	if (exclusive) ioctl(fd, EVIOCGRAB, 1); */

	/* Setup MPD connectivity */
	mpd = flabbybox_setup();
	if (!mpd)
	{
		dolog(LOG_ERR, "Couldn't contact MPD server\n");
/*		return 1; */
	}

		dolog(LOG_INFO, "Starting ...\n");



/*    if (dh == 0) return -1;*/

   dh = find_dev(); 


	/*
	 * Initialise the LCD display  */
	 
	 UsbLcd_init(0xEE17, 0x0001);

 
	/*
	 * Drop our root priveleges.
	 * We don't need them anymore anyways
	 */
	if (drop_uid != 0)
	{
		dolog(LOG_INFO, "Dropping userid to %u...\n", drop_uid);
		setuid(drop_uid);
	}
	if (drop_gid != 0)
	{
		dolog(LOG_INFO, "Dropping groupid to %u...\n", drop_gid);
		setgid(drop_gid);
	}

	flabbybox_menu = NULL;
	flabbybox_menu = add_menu(flabbybox_menu, "TopMenu");

	add_menu_entry(flabbybox_menu, "stop", f_stop, "");
	add_menu_entry(flabbybox_menu, "play", f_play, "");
	add_menu_entry(flabbybox_menu, "next", f_next, "");
	add_menu_entry(flabbybox_menu, "prev", f_prev, "");
	
	menu_pointer = (*flabbybox_menu).entries;



	while (running)
	{
		
		int key_pressed;

		if (!mpd) mpd = flabbybox_setup();

		key_pressed = read_key();

		if (key_pressed<0){
			updatedisplay();
			timeoutcount = timeoutcount - 1;
 			if (timeoutcount<0) {
			 timeoutcount=0;
			 DisplayMode =0;
                        }
 			continue;
		}

		timeoutcount = BrowseTime;

		dolog(LOG_DEBUG,"Key_pressed = %d\n", key_pressed);
		

		/* Lookup the key in our table */
		i = 0;
		while (	i < maxevent && events[i].code != key_pressed )
		{
			i++;
		}

		if (i < maxevent) evt = &events[i];
		else evt = NULL;

/*		if (	(evt != NULL && verbosity > 2) ||
			(evt == NULL && verbosity > 5)  )*/
		{
			char			buf[1024];
			unsigned int		n = 0;
			struct flabbybox_mapping	*map = NULL, *val = NULL;
			struct flabbybox_funcs	*func = func_map;

			if (ev.type == EV_KEY)
			{
				map = key_event_map;
				val = key_value_map;
			}

			if (map)
			{
				for (i=0; map[i].code != flabbybox_MAPPING_END && map[i].code != ev.code; i++);
				map = &map[i];
			}

			if (val)
			{
				for (i=0; val[i].code != flabbybox_MAPPING_END && val[i].code != ev.value; i++);
				val = &val[i];
			}

			if (evt)
			{
				for (i=0; func[i].name != NULL && func[i].function != evt->action; i++);
				func = &func[i];
			}

			n += snprintf(&buf[n], sizeof(buf)-n, "Event: T%lu.%06lu, type %u, code %u, value %d",
				ev.time.tv_sec, ev.time.tv_usec, ev.type,
				ev.code, ev.value);

			if (map)
			{
				n += snprintf(&buf[n], sizeof(buf)-n, ": %s, name: %s, label: %s",
					val ? val->name : "<unknown value>",
					map->name ? map->name : "<unknown name>",
					map->label ? map->label : "");
			}

			if (evt)
			{
				n += snprintf(&buf[n], sizeof(buf)-n, ", action: %s(%s)",
					func->name ? func->name : "?",
					evt->args ? evt->args : "");
			}

			dolog(LOG_DEBUG, "%s\n", buf);
		}

		if (evt == NULL) continue;
		evt->action(evt->args);
	}

	dolog(LOG_INFO, "flabbybox shutting down\n");

	mpd_closeConnection(mpd);

	close(fd);

	UsbLcd_deinit();

	return 0;
}

